package com.dolphin.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.setting.dialect.Props;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dolphin.commons.*;
import com.dolphin.mapper.DataBaseBackupMapper;
import com.dolphin.model.DataBaseBackup;
import com.dolphin.service.DataBaseBackupService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.util.Date;
import java.util.List;

/**
 * 描述:
 * ${DESCRIPTION}
 *
 * @author 小海豚
 * @create 2022-10-05 23:01
 * 个人博客地址：https://www.nonelonely.com
 */
@Service
public class DataBaseBackupServiceImpl extends ServiceImpl<DataBaseBackupMapper, DataBaseBackup> implements DataBaseBackupService {
    private final Logger logger = LoggerFactory.getLogger(DataBaseBackupServiceImpl.class);

    @Autowired
    private DataBaseBackupMapper dataBaseBackupMapper;

    @Override
    public List<DataBaseBackup> selectBackupsList() {
        return dataBaseBackupMapper.selectBackupsList();
    }

    @Override
    public Object mysqlBackups() throws Exception {
        String savePath = Constants.UPLOAD_TEMP_PATH;
        File dir = new File(savePath);
        if (!dir.exists()) {
            boolean mkdirs = dir.mkdirs();
            if (!mkdirs) {
                return ResponseBean.fail("创建临时目录失败", null);
            }
        }
        if (!savePath.endsWith(File.separator)) {
            savePath = savePath + File.separator;
        }
        String userName = this.getDbSetting().getStr("username");
        String password = this.getDbSetting().getStr("password");
        // 获取ip
        final String ip = this.getDbSetting().getStr("ip");
        // 获取端口号
        final String port = this.getDbSetting().getStr("port");
        // 获取数据库名称
        final String database_name = this.getDbSetting().getStr("dataBaseName");
        // 数据库文件名称
        StringBuilder mysqlFileName = new StringBuilder()
                .append("database")
                .append("_")
                .append(DateUtils.parseDateToStr("yyyy-MM-dd-HH-mm-ss", new Date()))
                .append(".sql");
        // 备份命令
        String[] commands = new String[3];
        if (SysUtil.isWindows()) {
            commands[0] = "cmd.exe";
            commands[1] = "/c";
        } else {
            commands[0] = "/bin/sh";
            commands[1] = "-c";
        }
        // 拼接命令
        StringBuilder mysqldump = new StringBuilder();
        mysqldump.append("mysqldump");
        mysqldump.append(" --opt");

        // 用户，密码
        mysqldump.append(" --user=").append(userName);
        mysqldump.append(" --password=").append(password);

        // ip，端口
        mysqldump.append(" --host=").append(ip);
        mysqldump.append(" --port=").append(port);

        // 使用的连接协议，包括：tcp, socket, pipe, memory
        mysqldump.append(" --protocol=tcp");

        // 设置默认字符集，默认值为utf8
        mysqldump.append(" --default-character-set=utf8");
        // 在导出数据之前提交一个BEGIN SQL语句，BEGIN 不会阻塞任何应用程序且能保证导出时数据库的一致性状态
        mysqldump.append(" --single-transaction=TRUE");

        // 导出存储过程以及自定义函数
        mysqldump.append(" --routines");
        // 导出事件
        mysqldump.append(" --events");

        // 数据库名
        mysqldump.append(" ").append(database_name);

        // 保存文件路径
        mysqldump.append(" > ").append(savePath).append(mysqlFileName);

        commands[2] = mysqldump.toString();

        DataBaseBackup smb = new DataBaseBackup();
        // 备份信息存放到数据库
        smb.setMysqlIp(ip);
        smb.setMysqlPort(port);
        smb.setBackupsName(String.valueOf(mysqlFileName));
        smb.setDatabaseName(database_name);
        smb.setMysqlCmd(String.valueOf(mysqldump));
        smb.setBackupsPath(savePath);
        smb.setCreateTime(DateTime.now());
        smb.setStatus(0);
        smb.setOperation(0);
        dataBaseBackupMapper.insert(smb);
        logger.error("数据库备份命令为：{}", mysqldump);
        // 获取Runtime实例
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(commands);
            if (process.waitFor() == 0) {
                smb.setStatus(1);
                dataBaseBackupMapper.updateById(smb);
                logger.info("Mysql 数据库备份成功,备份文件名：{}", mysqlFileName);
            } else {
                smb.setStatus(2);
                dataBaseBackupMapper.updateById(smb);
                throw new Exception("网络异常，数据库备份失败{}" + process.waitFor());
            }
        } catch (Exception e) {
            logger.error("数据库备份失败：{}", "网络异常，数据库备份失败");
            throw new Exception("网络异常，数据库备份失败");
        }
        return smb;
    }

    @Override
    public DataBaseBackup selectListId(Long id) {
        return dataBaseBackupMapper.selectListId(id);
    }

    @Override
    public Object rollback(DataBaseBackup smb) throws Exception {
        String userName = this.getDbSetting().getStr("username");
        String password = this.getDbSetting().getStr("password");
        // 备份路径和文件名
        StringBuilder realFilePath = new StringBuilder().append(smb.getBackupsPath()).append(smb.getBackupsName());
        if (!FileUtil.exist(String.valueOf(realFilePath))) {
            throw new Exception("文件不存在，恢复失败，请查看目录内文件是否存在后重新尝试！");
        }
        StringBuilder cmd = new StringBuilder()
                .append("mysql -h")
                .append(smb.getMysqlIp())
                .append(" -u")
                .append(userName)
                .append(" -p")
                .append(password)
                .append(" ")
                .append(smb.getDatabaseName())
                .append(" < ")
                .append(realFilePath);
        String[] command = new String[0];
        logger.error("数据库恢复命令为：{}", cmd);
        // 获取操作系统名称
        if (SysUtil.isWindows()) {
            // Windows
            command = new String[]{"cmd", "/c", String.valueOf(cmd)};
        } else {
            // Linux
            command = new String[]{"/bin/sh", "-c", String.valueOf(cmd)};
        }
        // 恢复指令写入到数据库
        smb.setMysqlBackCmd(String.valueOf(cmd));
        // 更新操作次数
        smb.setRecoveryTime(DateTime.now());
        smb.setOperation(smb.getOperation() + 1);
        // 获取Runtime实例
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(command);
            if (process.waitFor() == 0) {
                logger.error("Mysql 数据库恢复成功,恢复文件名：{}", realFilePath);
            } else {
                throw new Exception("网络异常，恢复失败，请稍后重新尝试！");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("网络异常，恢复失败，请稍后重新尝试！");
        }
        return smb;
    }

    @Override
    public Pager<DataBaseBackup> list(Pager<DataBaseBackup> pager) {
        PageHelper.startPage(pager.getPageIndex(), pager.getPageSize());
        QueryWrapper<DataBaseBackup> userQueryWrapper = new QueryWrapper<>(pager.getForm());
        List<DataBaseBackup> categories = this.list(userQueryWrapper);
        PageInfo<DataBaseBackup> pageInfo = new PageInfo<>(categories);
        pager.setTotal(pageInfo.getTotal());
        pager.setData(pageInfo.getList());
        pager.setCode(Pager.SUCCESS_CODE);
        return pager;
    }


    private Props getDbSetting() {
        File file = new File(Constants.DB_PROPERTIES_PATH);
        if (!file.exists()) {
            return null;
        }
        Props dbSetting = new Props(FileUtil.touch(file), CharsetUtil.CHARSET_UTF_8);
        return dbSetting;
    }

}
